// Copyright (c) 2011 Vratislav Cermak (vratacermak@gmail.com)
//  
// This file is part of SrtSync Project. See http://code.google.com/p/srtsync/ for more information.
//  
// SrtSync is an open source project. Using of SrtSync is free of charge.
// It is distributed under GNU General Public License version 3 (GPLv3).
// GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
//  
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the license for more details.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SrtSync.Core;
using SrtSync.Core.Framework;
using SrtSync.Core.Notifications;
using SrtSync.GtkWidgets;

namespace SrtSync.GtkProgram
{
    public partial class MainWindow : Gtk.Window, IApplicationView
    {
        private enum CommandTypes
        {
            Open,
            Save,
            SaveAs,
            SaveCopy,
            Close,
            Edit,
            Delete,
            Quit,
            Merge,
            SelectAll,
            DeselectAll,
            Synchronize,
            About,
            Encoding,
            Reload,
            ZoomIn,
            ZoomOut,
            DefaultZoom
        }

        private delegate void ActionDelegate();

        private readonly List<IObserver<Notification<ApplicationViewNotificationTypes>>> _observers = new List<IObserver<Notification<ApplicationViewNotificationTypes>>>();
        private readonly Dictionary<CommandTypes, Gtk.Action> _commands = new Dictionary<CommandTypes, Gtk.Action>();
        private readonly Gtk.ActionGroup _actionGroup = new Gtk.ActionGroup("Actions");
        private IApplication _application;
        private bool _skipEncodingHandler;
        private Gtk.Menu _encodingMenu;
        private bool _changingCursor;
        private bool _skipVisibleHandler;

        private bool Maximized { get; set; }

        public MainWindow() : base(Gtk.WindowType.Toplevel)
        {			
            Build();
            CreateActions();
            InitializeMenus();

            _leftTable.FocusInEvent += delegate 
            { 
                FocusedTable = _leftTable; 
                UpdateGui();
            };
            _rightTable.FocusInEvent += delegate 
            { 
                FocusedTable = _rightTable; 
                UpdateGui();
            };
            
            _leftTable.Changed += delegate { UpdateGui(); };
            _rightTable.Changed += delegate { UpdateGui(); };
        }
        
        public void Attach(IApplication application)
        {
            _application = application;
            _application.Register(this);
        }

        private void UpdateGui()
        {
            _commands[CommandTypes.Close].Sensitive = !CurrentEditor.Empty;
            _commands[CommandTypes.Delete].Sensitive = CurrentEditor.Selection.Count() > 0;
            _commands[CommandTypes.DeselectAll].Sensitive = CurrentEditor.Selection.Count() > 0;
            _commands[CommandTypes.Edit].Sensitive = CurrentEditor.Selection.Count() > 0 && CurrentEditor == _leftTable;
            _commands[CommandTypes.Merge].Sensitive = CurrentEditor.Selection.Count() > 0;
            _commands[CommandTypes.Save].Sensitive = !CurrentEditor.Empty && CurrentEditor.Modified;
            _commands[CommandTypes.SaveAs].Sensitive = !CurrentEditor.Empty;
            _commands[CommandTypes.SaveCopy].Sensitive = !CurrentEditor.Empty;
            _commands[CommandTypes.SelectAll].Sensitive = !CurrentEditor.Empty;
            _commands[CommandTypes.Synchronize].Sensitive = !_leftTable.Empty && !_rightTable.Empty;
            _commands[CommandTypes.Reload].Sensitive = !CurrentEditor.Empty;
            _commands[CommandTypes.DefaultZoom].Sensitive = CurrentEditorWidget.CanZoom(EditorWidget.ZoomTypes.Default);
            _commands[CommandTypes.ZoomIn].Sensitive = CurrentEditorWidget.CanZoom(EditorWidget.ZoomTypes.In);
            _commands[CommandTypes.ZoomOut].Sensitive = CurrentEditorWidget.CanZoom(EditorWidget.ZoomTypes.Out);
            
            UpdateStatusbar(CurrentEditor.Info);
        }
        
        private EditorWidget CurrentEditorWidget
        {
            get 
            {
                return (EditorWidget)CurrentEditor;
            }
        }
        
        private IEditorView CurrentEditor
        {
            get
            {
                if (FocusedTable == _rightTable)
                {
                    return _rightTable;
                }
                return _leftTable;
            }
        }
        
        private IEditorView OtherEditor
        {
            get
            {
                if (FocusedTable == _leftTable)
                {
                    return _rightTable;
                }
                return _leftTable;
            }
        }

        private void DispatchAction(EditorViewNotificationTypes action)
        {
            CurrentEditor.Dispatch(new EditorViewNotification {Type = action});
        }

        private void DispatchAction(EditorViewNotificationTypes action, Encoding encoding)
        {
            CurrentEditor.Dispatch(new EditorViewNotification { Type = action, Encoding = encoding });
        }

        private void ExecuteAction(ApplicationViewNotificationTypes action)
        {
            Notify(new Notification<ApplicationViewNotificationTypes> { Type = action });
        }

        private void AddAction(string label, string shortLabel, string accelerator, string stockId, CommandTypes commandType, ActionDelegate ad)
        {
            Gtk.Action action = new Gtk.Action(Guid.NewGuid().ToString(), label);
            action.ShortLabel = shortLabel;
            action.StockId = stockId;
            action.Data.Add(typeof(CommandTypes), commandType);
            action.AccelGroup = UIManager.AccelGroup;
            action.ConnectAccelerator();
            action.Activated += delegate { ad(); };
            action.Tooltip = label;

            _actionGroup.Add(action, accelerator);
            _commands.Add(commandType, action);
        }
        
        private void InitializeMenus()
        {
            Gtk.Menu fileMenu = new Gtk.Menu();
            Gtk.Menu editMenu = new Gtk.Menu();
            Gtk.Menu toolsMenu = new Gtk.Menu();
            Gtk.Menu helpMenu = new Gtk.Menu();
            Gtk.Menu viewMenu = new Gtk.Menu();
            _encodingMenu = new Gtk.Menu();

            _menu.AddTopMenuItem(fileMenu, "File");
            fileMenu.AddMenuItem(_commands[CommandTypes.Open]);
            fileMenu.AddSeparator();
            fileMenu.AddMenuItem(_commands[CommandTypes.Save]);
            fileMenu.AddMenuItem(_commands[CommandTypes.SaveAs]);
            fileMenu.AddMenuItem(_commands[CommandTypes.SaveCopy]);
            fileMenu.AddMenuItem(_commands[CommandTypes.Reload]);
            fileMenu.AddSeparator();
            fileMenu.AddMenuItem(_commands[CommandTypes.Close]);
            fileMenu.AddMenuItem(_commands[CommandTypes.Quit]);

            _menu.AddTopMenuItem(editMenu, "Edit");
            editMenu.AddMenuItem(_commands[CommandTypes.Edit]);
            editMenu.AddMenuItem(_commands[CommandTypes.Merge]);
            editMenu.AddMenuItem(_commands[CommandTypes.Delete]);
            editMenu.AddSeparator();
            editMenu.AddMenuItem(_commands[CommandTypes.SelectAll]);
            editMenu.AddMenuItem(_commands[CommandTypes.DeselectAll]);
            
            Gtk.MenuItem viewTopMenuItem = _menu.AddTopMenuItem(viewMenu, "View");
            Gtk.CheckMenuItem statusBarMenuItem = viewMenu.AddCheckMenuItem("Statusbar");
            viewTopMenuItem.Activated += delegate
            {
                try
                {
                    _skipVisibleHandler = true;
                    
                    statusBarMenuItem.Active = _statusbar.Visible;
                }
                finally
                {
                    _skipVisibleHandler = false;
                }
            };
            statusBarMenuItem.Activated += delegate
            {
                if (!_skipVisibleHandler)
                {
                    _statusbar.Visible = !_statusbar.Visible;
                }
            };
            viewMenu.AddSeparator();
            viewMenu.AddMenuItem(_commands[CommandTypes.ZoomIn]);
            viewMenu.AddMenuItem(_commands[CommandTypes.ZoomOut]);
            viewMenu.AddMenuItem(_commands[CommandTypes.DefaultZoom]);

            Gtk.MenuItem encodingTopMenuItem = _menu.AddTopMenuItem(_encodingMenu, "Encoding");
            encodingTopMenuItem.Activated += OnEncodingMenuItemActivated;

            foreach (Encoding encoding in Encodings.Items)
            {
                Encoding copy = encoding;
                Gtk.MenuItem mi = _encodingMenu.AddMenuItem(encoding);
                mi.Data.Add(typeof(CommandTypes), CommandTypes.Encoding);
                mi.Activated += delegate
                {
                    if (!_skipEncodingHandler)
                    {
                        DispatchAction(EditorViewNotificationTypes.Encoding, copy);
                    }
                    
                    UpdateEncodingMenu();
                };
            }

            _menu.AddTopMenuItem(toolsMenu, "Tools");
            toolsMenu.AddMenuItem(_commands[CommandTypes.Synchronize]);

            _menu.AddTopMenuItem(helpMenu, "Help");
            helpMenu.AddMenuItem(_commands[CommandTypes.About]);

            _toolbar.Add(_commands[CommandTypes.Open].CreateToolItem());
            _toolbar.Add(_commands[CommandTypes.Save].CreateToolItem());
            _toolbar.Add(_commands[CommandTypes.Reload].CreateToolItem());
            _toolbar.Add(new Gtk.SeparatorToolItem());
            _toolbar.Add(_commands[CommandTypes.Edit].CreateToolItem());
            _toolbar.Add(_commands[CommandTypes.Delete].CreateToolItem());
            _toolbar.Add(new Gtk.SeparatorToolItem());
            _toolbar.Add(_commands[CommandTypes.Synchronize].CreateToolItem());
            _toolbar.Add(new Gtk.SeparatorToolItem());
            _toolbar.Add(_commands[CommandTypes.ZoomIn].CreateToolItem());
            _toolbar.Add(_commands[CommandTypes.ZoomOut].CreateToolItem());            

            _menu.ShowAll();
            _toolbar.ShowAll();
        }

        void CreateActions()
        {
            // actions for controller
            AddAction("Open...", "Open", null, "gtk-open", CommandTypes.Open, () => DispatchAction(EditorViewNotificationTypes.Open));
            AddAction("Save", null, null, "gtk-save", CommandTypes.Save, () => DispatchAction(EditorViewNotificationTypes.Save));
            AddAction("Save As...", null, null, "gtk-save-as", CommandTypes.SaveAs, () => DispatchAction(EditorViewNotificationTypes.SaveAs));
            AddAction("Save Copy...", null, null, null, CommandTypes.SaveCopy, () => DispatchAction(EditorViewNotificationTypes.SaveCopy));
            AddAction("Close", null, null, "gtk-close", CommandTypes.Close, () => DispatchAction(EditorViewNotificationTypes.Close));
            AddAction("Edit...", "Edit", "Return", "gtk-edit", CommandTypes.Edit, () => DispatchAction(EditorViewNotificationTypes.Edit));
            AddAction("Delete", null, "Delete", "gtk-delete", CommandTypes.Delete, () => DispatchAction(EditorViewNotificationTypes.Delete));
            AddAction("Quit", null, null, "gtk-quit", CommandTypes.Quit, () => ExecuteAction(ApplicationViewNotificationTypes.Quit));
            AddAction("Merge", null, "<Control>M", null, CommandTypes.Merge, () => DispatchAction(EditorViewNotificationTypes.Merge));            
            AddAction("Synchronize", null, null, "gtk-execute", CommandTypes.Synchronize, () => ExecuteAction(ApplicationViewNotificationTypes.Synchronize));
            AddAction("About", null, null, "gtk-about", CommandTypes.About, () => ExecuteAction(ApplicationViewNotificationTypes.About));
            AddAction("Reload", null, "F5", "gtk-refresh", CommandTypes.Reload, () => DispatchAction(EditorViewNotificationTypes.Reload));
         
            // actions for widgets
            AddAction("Select All", null, "<Control>A", null, CommandTypes.SelectAll, () => 
            {
                CurrentEditorWidget.SelectAll();
                UpdateGui();
            });
            AddAction("Deselect All", null, "<Shift><Control>A", null, CommandTypes.DeselectAll, () => 
            {
                CurrentEditorWidget.DeselectAll();
                UpdateGui();
            });
            AddAction("Zoom In", null, "<Control>plus", "gtk-zoom-in", CommandTypes.ZoomIn, () => 
            {
                _leftTable.Zoom(EditorWidget.ZoomTypes.In);
                _rightTable.Zoom(EditorWidget.ZoomTypes.In);
                UpdateGui();
            });
            AddAction("Zoom Out", null, "<Control>minus", "gtk-zoom-out", CommandTypes.ZoomOut, () => 
            {
                _leftTable.Zoom(EditorWidget.ZoomTypes.Out);
                _rightTable.Zoom(EditorWidget.ZoomTypes.Out);
                UpdateGui();
            });
            AddAction("Normal Size", null, "<Control>0", null, CommandTypes.DefaultZoom, () => 
            {
                _leftTable.Zoom(EditorWidget.ZoomTypes.Default);
                _rightTable.Zoom(EditorWidget.ZoomTypes.Default);
                UpdateGui();
            });
         
            UIManager.InsertActionGroup(_actionGroup, 0);
        }

        void UpdateEncodingMenu()
        {
            try
            {
                _skipEncodingHandler = true;
         
                foreach (Gtk.CheckMenuItem chmi in _encodingMenu.AllChildren.Cast<Gtk.Widget>().Where(x => x is Gtk.CheckMenuItem))
                {
                    chmi.Active = ((Encoding)chmi.Data[typeof(Encoding)]).CodePage == CurrentEditor.Encoding.CodePage;
                }
            }
            finally
            {
                _skipEncodingHandler = false;
            }
        }

        void OnEncodingMenuItemActivated(object sender, EventArgs e)
        {
            UpdateEncodingMenu();
        }

        private void OnWindowStateEvent(object sender, Gtk.WindowStateEventArgs args)
        {
            Maximized = args.Event.NewWindowState == Gdk.WindowState.Maximized;
        }

        private Gtk.NodeView FocusedTable { get; set; }

        public IEditorView Left
        {
            get
            {
                return _leftTable;
            }
        }

        public IEditorView Right
        {
            get
            {
                return _rightTable;
            }
        }
        
        public void Register(IObserver<Notification<ApplicationViewNotificationTypes>> observer)
        {
            _observers.Add(observer);
        }

        public void UnRegister(IObserver<Notification<ApplicationViewNotificationTypes>> observer)
        {
            _observers.Remove(observer);
        }
        
        public void Notify(Notification<ApplicationViewNotificationTypes> notification)
        {
            foreach (IObserver<Notification<ApplicationViewNotificationTypes>> observer in _observers)
            {
                observer.Process(notification);
            }
        }

        protected void OnShown(object sender, EventArgs e)
        {
            UpdateGui();
        }

        public void Process(Notification<ApplicationModelNotificationTypes> data)
        {
            data.Type.CheckValue();

            switch (data.Type)
            {   
                case ApplicationModelNotificationTypes.Start:
                    UseSettings();
                    Show();
                    break;
                case ApplicationModelNotificationTypes.Terminate:
                    UpdateSettings();
                    Gtk.Application.Quit();
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private void UpdateSettings()
        {
            if (Maximized)
            {
                _application.Settings.Maximized = true;
            }
            else
            {
                int width, height, x, y;
                GetSize(out width, out height);
                GetPosition(out x, out y);

                _application.Settings.Size = new System.Drawing.Size(width, height);
                _application.Settings.Location = new System.Drawing.Point(x, y);
                _application.Settings.Maximized = false;
            }
            
            _application.Settings.Statusbar = _statusbar.Visible;
        }

        private void UseSettings()
        {
            if (!_application.FirstRun)
            {
                if (!_application.Settings.Maximized)
                {
                    Move(_application.Settings.Location.X, _application.Settings.Location.Y);
                    Resize(_application.Settings.Size.Width, _application.Settings.Size.Height);
                }
                else
                {
                    Maximize();
                }
                
                _statusbar.Visible = _application.Settings.Statusbar;
            }
            
            // fix splitter position            
            int width, height;
            GetSize(out width, out height);            
            _hpaned.Position = width / 2;
        }

        protected void OnDeleteEvent(object o, Gtk.DeleteEventArgs args)
        {
            ExecuteAction(ApplicationViewNotificationTypes.Quit);
        }

        protected void OnEditorRowActivated(object o, Gtk.RowActivatedArgs args)
        {
            DispatchAction(EditorViewNotificationTypes.Edit);
        }
     
        protected void OnCursorChanged(object sender, System.EventArgs e)
        {
            if (_changingCursor)
            {
                return;
            }
         
            try
            {
                _changingCursor = true;
                
                // cleaner but slower solution
                // ((EditorWidget)OtherEditor).SelectedIndex = ((EditorWidget)CurrentEditor).SelectedIndex;
                
                Gtk.TreePath path;
                Gtk.TreeViewColumn column;
             
                ((Gtk.NodeView)CurrentEditor).GetCursor(out path, out column);
                if (path != null)
                {
                    ((Gtk.NodeView)OtherEditor).SetCursor(path, ((Gtk.NodeView)OtherEditor).Columns[0], false);
                }
            }
            finally
            {
                _changingCursor = false;
            }
        }
        
        private void UpdateStatusbar(string message)
        {
            _statusbar.Pop(1);
            _statusbar.Push(1, message);
        }
    }
}